Day 21,我們沒有把整個 Repository 全部塞進 Prompt,而是根據 SEC-AUTHZ-001 的必要證據建立 context.json。
Agent 現在取得:
Firestore 授權規則
Project 的 ownerId 寫入位置
projects Collection 的讀取路徑
Browser 到 Firestore 的信任邊界
接下來就能交給 Gemini 找問題了嗎?
如果使用單一 Agent,Prompt 可能變成:
請先理解系統架構,
選擇適用規則,
尋找 Production 問題,
執行必要測試,
推翻可能的誤報,
決定嚴重度,
最後輸出報告。
這段 Prompt 把研究、懷疑、驗證與裁決全部交給同一個角色。
模型一旦在前面形成:
這裡存在 Broken Access Control。
後面的驗證就容易變成尋找支持原判斷的證據,而不是認真嘗試推翻它。
今天要把 Vibe Guard 拆成第一版多 Agent Workflow:
Orchestrator
→ Recon Agent
→ Context Agent
→ Hunter Agent
→ Validator Agent
→ Reporter Agent
每個 Agent 都只有一項主要責任、明確輸入、固定輸出與有限權限。
其中最重要的規則是:
Hunter 只能提出 Candidate,不能自行把它升級為 Confirmed Finding。
把所有工作交給同一個 Agent,確實會讓 System Instruction 越來越長。
但更大的問題是角色互相衝突。
Recon 的目標是建立中立的系統模型:
有哪些元件?
資料如何流動?
授權在哪裡執行?
哪些資訊仍未知?
Hunter 的目標則是主動懷疑:
攻擊者能控制什麼?
哪條路徑可能跨越邊界?
缺少哪個控制可能造成影響?
Validator 必須採取相反立場:
這條路徑真的可達嗎?
是否有其他控制阻擋?
測試能否重現?
原始碼引用是否正確?
Reporter 又有另一種任務:
哪些結果通過門檻?
如何排序與呈現?
哪些 Candidate 被拒絕?
如果同一段對話同時承擔這些目標,會出現幾種風險。
第一,Confirmation Bias。
模型提出 Candidate 後,後續容易延續自己的推理,而不是重新質疑前提。
第二,權限過大。
負責讀程式碼的 Agent,不一定需要執行 Shell。
負責輸出報告的 Agent,更不應擁有修改 Repository 或部署環境的權限。
第三,Context 汙染。
Recon 的大量架構資訊、Hunter 的假設、測試 Log 與最終報告格式全部留在同一段 History,會讓每個階段都背負不需要的 Token 與不可信文字。
第四,失敗難以定位。
結果錯誤時,很難分辨是:
Recon 漏掉信任邊界
Context Selector 漏掉必要檔案
Hunter 沒有提出 Candidate
Validator 沒有執行正確測試
Reporter 改寫了 Verdict
第五,輸出沒有穩定的交接契約。
前一階段用 Markdown 說明,下一階段只能再請模型自行解讀,錯誤會沿著 Workflow 放大。
多 Agent 的價值不是讓更多模型同時說話,而是把責任、Context、工具與錯誤邊界拆開。
Agent 負責在有限範圍內完成一項工作。
Workflow 負責決定:
誰先執行?
誰可以讀取哪些 Artifact?
哪個結果能進入下一步?
何時停止?
失敗後可以重試還是必須終止?
這種控制流程不一定要交給 LLM。
Google Agent Development Kit 的 Workflow 文件將 Predictability、Reliability、Structure 與限制各 Agent 的 Context,列為組合多個 Agent 與執行節點的主要優點。
ADK 支援幾種 Workflow 結構:
| Workflow | 適合情境 |
|---|---|
| Sequential | 有固定先後相依,例如先 Recon 再 Hunt |
| Parallel | 工作彼此獨立,例如 Security、Privacy、Reliability Hunter |
| Loop | 反覆補證據,直到完成或達到停止條件 |
| Graph-based | 需要分支、合併、失敗路徑與條件轉移 |
| Collaborative | 由 Coordinator 動態選擇 Sub-agent |
Day 22 使用固定的 Sequential Workflow。
原因是目前每一步都有清楚相依:
Recon 沒完成
→ 無法判斷規則是否適用
Context 不完整
→ Hunter 不應開始
沒有 Candidate
→ Validator 沒有驗證目標
Validator 未接受
→ Reporter 不得列為 Finding
這種流程若讓模型自由決定執行順序,反而增加不必要的不確定性。
因此第一版 Orchestrator 使用 Deterministic JavaScript,而不是再建立一個可以跳過步驟的 LLM Agent。
第一個角色是 Recon Agent。
它只負責輸出系統事實:
Application Type
Actor
Input Surface
Data Flow
Trust Boundary
Authorization Control
Unknown
Recon Agent 不輸出漏洞名稱與嚴重度。
第二個角色是 Context Agent。
它根據 Rule 的 requiredEvidence 選取:
相關架構事實
Source File
Symbol 或行號範圍
已排除內容
證據覆蓋率
Context Agent 不判斷程式安全與否。
第三個角色是 Hunter Agent。
它使用 Context 建立 Candidate:
{
"id": "AUTH-01",
"ruleId": "SEC-AUTHZ-001",
"verdict": "candidate",
"hypothesis": "Any authenticated user may read and modify another user's project.",
"evidence": [],
"requestedValidation": []
}
Hunter 可以大膽提出攻擊假設,但 verdict 只能是 candidate。
第四個角色是 Validator Agent。
它不能只接受 Hunter 的文字結論,而要:
只有 Validator 可以輸出:
confirmed
requires_evidence
hardening
rejected
第五個角色是 Reporter Agent。
它只能讀取 Validator 接受的 Review,不能把被拒絕的 Candidate 改寫成漏洞,也不能自行提高 Severity。
它負責:
計算 Confirmed Finding
依嚴重度排序
產生摘要
連結 Evidence 與 Artifact
設定整體 Workflow Status
Day 22 的 Orchestrator 使用固定狀態轉移:
START
→ RECON_COMPLETE
→ CONTEXT_READY
→ CANDIDATE_CREATED
→ VALIDATION_COMPLETE
→ REPORT_READY
→ DONE
每一步都必須先通過 Schema 與 Business Rule。
概念上可以寫成:
const reconArtifact = await runReconAgent();
const contextArtifact = await runContextAgent(reconArtifact);
const candidateArtifact = runHunterAgent(contextArtifact);
const validationArtifact = await runValidatorAgent(candidateArtifact);
const reportArtifact = runReporterAgent(validationArtifact);
Orchestrator 不負責重新解釋內容。
它只負責:
傳入允許的 Artifact
檢查輸出契約
記錄 Timeline
處理 Timeout 與錯誤
決定是否進入下一個 Node
這可以避免 Coordinator 自己成為另一個擁有所有 Context、所有工具與所有判斷權的超級 Agent。
最簡單的 Multi-agent Demo 常這樣設計:
Agent A 寫一段 Markdown
Agent B 讀完整聊天紀錄
Agent C 再閱讀 A 與 B 的所有思考
這種方法很快,卻有三個問題。
第一,無法保證下一個 Agent 取得必要欄位。
第二,不相關內容也會被一起傳遞。
第三,無法區分資料、指令與 Agent 的自由文字。
Day 22 改用 Artifact 交接。
Recon Artifact 的最小格式為:
const reconArtifactSchema = z.object({
application: z.string().min(1),
trustBoundary: z.string().min(1),
authorizationControl: evidenceSchema,
unknowns: z.array(z.string())
}).strict();
Hunter Artifact 則限制:
const candidateArtifactSchema = z.object({
id: z.literal("AUTH-01"),
ruleId: z.literal("SEC-AUTHZ-001"),
verdict: z.literal("candidate"),
hypothesis: z.string().min(1),
evidence: z.array(evidenceSchema).min(1),
requestedValidation: z.array(z.string()).min(1)
}).strict();
z.literal("candidate") 是一道重要邊界。
即使 Hunter 認為問題十分明顯,它也不能輸出:
{
"verdict": "confirmed"
}
這不是靠 Prompt 中的「請不要」維持,而是由 Runtime Schema 拒絕。
Workflow 仍然需要 State,例如:
目前執行到哪一步
Run ID
開始時間
已使用 Token
重試次數
整體 Status
但不應把所有大型原始碼、模型回答與測試 Log 都塞進同一個 Mutable State。
比較清楚的分工是:
| 類型 | 範例 |
|---|---|
| State | currentStep、status、retryCount |
| Artifact | architecture.json、context.json、candidate.json |
| Event | Agent 開始、工具呼叫、驗證失敗、狀態轉移 |
| Secret | API Key、短期 Credential,不寫入 Artifact |
Google ADK 將 Session State 描述為可序列化的 Scratchpad,並區分 Session、User、App 與單次 Invocation 的 Temporary State。
無論使用哪個 Framework,都應避免在共享 State 放入:
Database Connection
Function
SDK Client
未遮罩的 Secret
無限制成長的完整對話
Day 22 Demo 把每階段結果寫成獨立 JSON:
multi-agent-demo/output/
├── candidate.json
├── validated-review.json
└── workflow-run.json
這些檔案可以分別回答:
Hunter 原本提出什麼?
Validator 接受了哪些證據?
Workflow 依什麼順序完成?
多 Agent 如果共用同一組高權限工具,只是把風險複製多份。
第一版可以使用以下權限表:
| Agent | Read Source | Search | Execute Test | Write Source | Publish Report |
|---|---|---|---|---|---|
| Recon | 是 | 是 | 否 | 否 | 否 |
| Context | 是 | 是 | 否 | 否 | 否 |
| Hunter | 僅選定 Context | 否 | 否 | 否 | 否 |
| Validator | 是 | 是 | 受限測試 | 否 | 否 |
| Reporter | 僅驗證結果 | 否 | 否 | 否 | 是 |
Hunter 不需要 Shell。
Reporter 不需要讀取整個 Repository。
Validator 的測試工具也不應等同任意命令執行,而應是受限 Function:
run_authorization_test({
rulesPath,
scenarioId
})
Gemini Function Calling 可以讓模型選擇工具並產生符合 Function Schema 的參數,但真正執行工具的 Host Application 仍要驗證:
工具名稱是否允許?
Path 是否位於 Repository?
Scenario 是否存在 Allowlist?
是否需要網路?
是否會修改資料?
Timeout 與輸出上限是多少?
模型要求呼叫 Function,不等於模型自動取得 Function 背後的所有權限。
專案新增:
demo-app/
└── multi-agent-demo/
├── scenario.js
└── output/
├── candidate.json
├── validated-review.json
└── workflow-run.json
執行:
cd /media/mickey/777/ithome/demo-app
npm run multi-agent:demo
Script 會先執行 Day 18 與 Day 21 的產物流程:
recon-demo/architecture.json
context-selection-demo/context.json
接著啟動 Firestore Emulator,執行 Multi-agent Workflow。
package.json 的指令為:
{
"scripts": {
"multi-agent:demo": "npm run context-selection:demo >/dev/null && firebase emulators:exec --only firestore \"node multi-agent-demo/scenario.js\""
}
}
這裡刻意讓 Emulator 生命週期由 firebase emulators:exec 管理。
測試完成後 Emulator 會停止,不需要讓 Validator 取得管理背景程序的權限。
Recon Agent 讀取 architecture.json,選出 Firestore Trust Boundary:
const firestoreBoundary = architecture.trustBoundaries.find(
(boundary) => boundary.to === "Cloud Firestore emulator"
);
輸出內容只有:
Local-only Firebase web application
Browser -> Cloud Firestore emulator
firestore.rules:6
4 個 Repository 外的 Unknown
它不會因為看到 request.auth != null 就產生 Finding。
Recon 的完成條件是系統模型可供後續使用,不是找到問題。
Context Agent 接收 Recon Artifact,再讀取 Day 21 的 Context Bundle。
它會先確認兩份 Artifact 對授權控制的描述一致:
if (
reconArtifact.authorizationControl.path !==
bundle.architecture.authorization.path
) {
throw new Error(
"Context Agent received conflicting authorization evidence"
);
}
接著驗證三項 Coverage:
firestore.rules
project ownership field
read and write query path
缺少任何一項就停止 Workflow。
這裡不能使用:
Context 大概夠了,讓 Hunter 自己推測。
因為 Hunter 的任務是尋找問題,不是補完被 Retrieval 漏掉的世界觀。
Hunter 只能看到 Context Agent 選定的兩個 Source File。
它找到:
allow read, write: if request.auth != null;
以及:
ownerId: auth.currentUser.uid
與:
const projectsQuery = query(collection(db, "projects"));
因此提出:
{
"id": "AUTH-01",
"ruleId": "SEC-AUTHZ-001",
"verdict": "candidate",
"hypothesis": "Any authenticated user may read and modify a project owned by another account.",
"requestedValidation": [
"Use two authenticated accounts.",
"Read a project owned by the other account.",
"Modify a project owned by the other account."
]
}
這份 Artifact 同時說明「為什麼懷疑」與「下一步如何證明」。
它仍然不是最終報告。
Validator 不使用 Hunter 的 Confidence 作為證據。
它重新讀取:
firestore.rules
再建立兩個測試身分:
const ownerDb = testEnvironment
.authenticatedContext("owner-user")
.firestore();
const attackerDb = testEnvironment
.authenticatedContext("attacker-user")
.firestore();
測試資料明確保存:
{
"ownerId": "owner-user"
}
接著執行:
owner-user 讀取自己的 Project
attacker-user 讀取 owner-user 的 Project
attacker-user 修改 owner-user 的 Project
只有三項結果都符合攻擊假設,才建立 confirmed Review:
const confirmed =
ownerRead.allowed &&
attackerRead.allowed &&
attackerWrite.allowed;
最後仍要通過 Day 20 的:
await validateReview(review, repositoryRoot);
這會再次核對 Structured Output、Evidence Path、Line、Snippet 與 Confirmed Finding 的必要欄位。
多 Agent 不代表可以移除既有 Validator。
相反地,每個 Agent Boundary 都應該增加可驗證契約。
Reporter 只讀取:
validationArtifact.review.findings
如果 Validator 沒有接受 Finding,Reporter 必須輸出:
No confirmed findings.
它不能因為 Hunter 的 Candidate 看起來嚴重,就把它補回報告。
目前整體狀態使用:
沒有 Confirmed Finding → passed
至少一個 Confirmed Finding → failed
Workflow 無法完成 → error
failed 與 error 必須分開。
failed 表示檢查成功完成,而且找到不符合上線門檻的問題。
error 表示 Agent、工具、Schema、Timeout 或 Artifact 發生錯誤,這次沒有產生可信結論。
不能把 Error 改寫成:
{
"findings": []
}
否則 Pipeline 會把「沒有完成檢查」誤認成「檢查通過」。
實際 Workflow 輸出:
MULTI-AGENT PRODUCTION READINESS WORKFLOW
Orchestrator START fixed sequential workflow
Recon Agent PASS 1 trust boundary, 4 unknowns
Context Agent PASS 2 source files cover 3 requirements
Hunter Agent CANDIDATE AUTH-01 requests 3 checks
Validator Agent CONFIRMED AUTH-01 passed source and two-account execution checks
Reporter Agent PASS 1 confirmed finding
Orchestrator DONE workflow status=failed
EXECUTION EVIDENCE
owner-user reads own project: ALLOWED
attacker-user reads owner project: ALLOWED
attacker-user edits owner project: ALLOWED
FINAL REPORT
HIGH AUTH-01: Any signed-in user can access another user's project
Artifacts: multi-agent-demo/output/
結果中最容易誤解的是:
Reporter Agent PASS
Orchestrator workflow status=failed
Reporter PASS 代表它成功完成自己的工作。
整體 failed 則代表 Production Readiness Gate 找到一項已確認的 High Finding。
Agent 執行狀態與產品檢查結果是兩個不同維度。
Day 22 先使用 Sequential Workflow,但完整 Vibe Guard 不必所有工作都排隊。
完成 Recon 後,可以平行執行:
Security Hunter
Privacy Hunter
Reliability Hunter
Deployment Hunter
AI Safety Hunter
前提是它們:
Google ADK 的 Parallel Workflow 文件也提醒,平行 Sub-agent 之間不會自動共享對話與狀態;若需要協調,必須明確設計 Shared Context、External State 或後處理。
因此不能假設:
Security Hunter 發現 ownerId,
Privacy Hunter 自然就會知道。
如果兩者需要共用資料,應由 Recon Artifact 或後續 Merge Node 提供,而不是依靠平行執行時的隱性溝通。
有些 Candidate 第一次沒有足夠證據:
缺少部署環境設定
缺少 SDK Timeout 預設值
缺少 IAM Policy
缺少動態測試
可以設計:
Hunter
→ Validator
→ Evidence Request
→ Context Agent 補資料
→ Validator 再判斷
但 Loop 必須有停止條件:
最多重試 2 次
每次必須要求新的具體證據
Context Token 不得超過 Budget
同一工具錯誤不得無限重試
Repository 無法回答時輸出 requires_evidence
如果沒有停止條件,Agent 可能重複搜尋同一個檔案,直到耗盡 Token、時間或 API Quota。
不同錯誤不應使用相同處理方式。
| 失敗 | 建議處理 |
|---|---|
| JSON 不符合 Schema | 同一步有限次重試 |
| Evidence 行號過期 | 重新 Retrieval,不接受舊 Finding |
| 測試 Timeout | 標記 Error,保存 Log |
| 缺少 Repository 外設定 | requires_evidence |
| Candidate 被反證 | rejected |
| Agent 無權限呼叫工具 | 拒絕並記錄 Policy Event |
| API 暫時性錯誤 | 帶 Backoff 的有限次重試 |
最重要的是保留 Fail Closed 的語意。
Production Readiness Workflow 無法確認結果時,不應自動回傳:
Ready for production.
可以回傳:
Assessment incomplete.
Missing evidence: production IAM policy.
不一定。
Multi-agent 是責任與執行邊界,不等於一定要使用五個不同 Model。
可以依任務選擇:
Recon:較快模型 + Deterministic Parser
Context:Search、AST、Code Graph,模型只負責分類
Hunter:擅長跨檔案推理的模型
Validator:Deterministic Test + 獨立模型覆核
Reporter:較快模型或純程式模板
甚至部分 Node 完全不需要 LLM。
Day 22 的 Orchestrator、Schema Validation、Source Evidence Check 與 Firestore Test 都是 Deterministic Code。
Gemini 最適合負責:
理解不同技術棧
提出攻擊假設
解釋跨檔案資料流
把缺少的證據轉成具體 Tool Request
不是負責取代所有可以精確執行的程式。
今天完成的是單一規則、單一 Candidate 的 Sequential Workflow。
正式版本還需要:
目前 Validator 已與 Hunter 分離,但仍使用 Hunter 選出的 Source Evidence。
明天會更進一步:讓另一個 Agent 採用對抗立場與獨立 Retrieval,主動尋找反證,而不是只執行 Hunter 指定的驗證步驟。
多 Agent 的重點不是 Agent 數量,而是清楚回答:
今天的 Vibe Guard Workflow 使用:
Recon Agent
→ 建立 Firestore Trust Boundary
Context Agent
→ 確認三項必要證據完整
Hunter Agent
→ 提出 AUTH-01 Candidate
Validator Agent
→ 重新核對 Source 並執行雙帳號測試
Reporter Agent
→ 只輸出 Validator 接受的 Finding
最後,attacker-user 成功讀取並修改 owner-user 的 Project。
因此 Validator 將 Candidate 升級為 High Confirmed Finding,而 Orchestrator 將 Production Readiness Workflow 標記為 failed。
這份結果不是同一個 Agent 對自己說「我同意我的判斷」,而是經過角色隔離、Artifact 契約與動態測試的可追蹤流程。
明天,我們會讓另一個 Agent 專門挑錯,使用對抗驗證淘汰假警報。